import * as THREE from "three"
import CameraPoseProxy from "../CameraPoseProxy"
import transitionTypeEnum from "../enum/transitionType.enum"
import DollhouseControlsModule from "../module/DollhouseControlsModule"
import Vectors from "../three/Vectors"
import funcUtil from "../utils/func.util"
import OpenDeferred from "../utils/OpenDeferred"
import CommonControls from "./CommonControls"
import y from "../math/7170"
const D = 1000 / 60,
  C = 80 * y.Ue,
  P = 0 * y.Ue,
  I = Math.PI / 1000,
  N = 0.00001
export default class DollhouseCameraControls extends CommonControls {
  cameraPoseProxy: CameraPoseProxy
  cameraCheck: DollhouseControlsModule["cameraCheck"]
  tempOrientation: THREE.Quaternion
  tempAxis: THREE.Vector3
  nextPosition: THREE.Vector3
  nextOrientation: THREE.Quaternion
  currentOrientation: THREE.Quaternion
  currentPosition: THREE.Vector3
  positionDelta: THREE.Vector3
  angularAccel: THREE.Vector2
  angularVelocity: THREE.Vector2
  linearAccel: THREE.Vector2
  linearVelocity: THREE.Vector2
  zoomAccel: number
  zoomVelocity: number
  cameraOrientation: THREE.Euler
  aimTarget: THREE.Vector3
  poseController: any
  currentPhiLowerLimit: number
  currentPhiUpperLimit: number
  checkBounds: boolean
  _maxZoomDistance: number
  transition: {
    active: boolean
    startTime: number
    elapsed: number
    duration: number
    angularVelocity: THREE.Vector2
    linearVelocity: THREE.Vector2
    zoomVelocity: number
    easeOut: boolean
    deferred?: OpenDeferred
  }
  constructor(e: CameraPoseProxy, t: DollhouseControlsModule["cameraCheck"], i: THREE.Box3, checkBounds = !1) {
    super()
    this.cameraPoseProxy = e
    this.cameraCheck = t
    this.tempOrientation = new THREE.Quaternion()
    this.tempAxis = new THREE.Vector3()
    this.nextPosition = new THREE.Vector3()
    this.nextOrientation = new THREE.Quaternion()
    this.currentOrientation = new THREE.Quaternion()
    this.currentPosition = new THREE.Vector3()
    this.positionDelta = new THREE.Vector3()
    this.angularAccel = new THREE.Vector2()
    this.angularVelocity = new THREE.Vector2()
    this.linearAccel = new THREE.Vector2()
    this.linearVelocity = new THREE.Vector2()
    this.zoomAccel = 0
    this.zoomVelocity = 0
    this.cameraOrientation = new THREE.Euler()
    this.aimTarget = new THREE.Vector3()
    this.poseController = null
    this.currentPhiLowerLimit = P
    this.currentPhiUpperLimit = C
    this.checkBounds = checkBounds
    this._maxZoomDistance = Math.max(30, 1.5 * i.getSize(new THREE.Vector3()).length())
    this.transition = {
      active: !1,
      startTime: 0,
      elapsed: 0,
      duration: 0,
      angularVelocity: new THREE.Vector2(),
      linearVelocity: new THREE.Vector2(),
      zoomVelocity: 0,
      easeOut: !1
    }
  }
  onAccessGranted(e) {
    this.poseController = e
  }
  onAccessRevoked(e) {
    this.stop()
  }
  get maxZoomDistance() {
    return this._maxZoomDistance
  }
  setOrbitalAcceleration(e, t = !1) {
    this.transition.active ||
      (t && this.haltVelocity(e, this.angularVelocity),
      (this.angularAccel.x = void 0 !== e.x ? e.x : this.angularAccel.x),
      (this.angularAccel.y = void 0 !== e.y ? e.y : this.angularAccel.y))
  }
  setPanAcceleration(e, t = !1) {
    this.transition.active ||
      (t && this.haltVelocity(e, this.linearVelocity),
      (this.linearAccel.x = void 0 !== e.x ? e.x : this.linearAccel.x),
      (this.linearAccel.y = void 0 !== e.y ? e.y : this.linearAccel.y))
  }
  setZoomAcceleration(e) {
    this.transition.active || (this.zoomAccel = e)
  }
  async setPhiLimits(e, t, i) {
    this.currentPhiLowerLimit = e
    this.currentPhiUpperLimit = t
    const n = this.cameraPoseProxy.pose
    this.cameraOrientation.setFromQuaternion(n.rotation, "YXZ")
    const s = -this.cameraOrientation.x
    const r = funcUtil.getMax(0, this.currentPhiLowerLimit - s, this.currentPhiUpperLimit - s)
    Math.abs(r) > N && i && (await this.orbit(new THREE.Vector2(), !0))
  }
  haltVelocity(e, t) {
    e.x && t.x && Math.sign(e.x) !== Math.sign(t.x) && (t.x = 0)
    e.y && t.y && Math.sign(e.y) !== Math.sign(t.y) && (t.y = 0)
  }
  startTransition(e: number, t: THREE.Vector2, i: THREE.Vector2, n: number, s: boolean) {
    if (null === this.poseController) return OpenDeferred.reject("Unable to start transition, since controller is unavailable.")
    const r = new OpenDeferred()
    this.transition.active = !0
    this.transition.duration = e
    this.transition.elapsed = 0
    this.transition.startTime = Date.now()
    this.transition.deferred = r
    this.transition.angularVelocity.copy(t)
    this.transition.linearVelocity.copy(i)
    this.transition.zoomVelocity = n
    this.transition.easeOut = s
    this.angularAccel.set(0, 0)
    this.linearAccel.set(0, 0)
    this.zoomAccel = 0
    this.angularVelocity.copy(t)
    this.linearVelocity.copy(i)
    this.zoomVelocity = n
    this.poseController.beginExternalTransition()
    return r.promise()
  }
  stopTransition() {
    this.transition.active && (this.poseController && this.poseController.endExternalTransition(), (this.transition.active = !1))
    this.transition.deferred && (this.transition.deferred.resolve(), (this.transition.deferred = void 0))
  }
  updateTransition(e, t, i, n) {
    let s = 1
    let r = e / D
    this.transition.elapsed += e
    if (this.transition.elapsed >= this.transition.duration) {
      s = (this.transition.duration - (this.transition.elapsed - e)) / e
      r = 1
    }
    t && (this.angularVelocity.copy(this.transition.angularVelocity).multiplyScalar(s * r), this.orbit(this.angularVelocity))
    i && (this.linearVelocity.copy(this.transition.linearVelocity).multiplyScalar(s * r), this.pan(this.linearVelocity))
    n && ((this.zoomVelocity = this.transition.zoomVelocity * s * r), this.zoom(this.zoomVelocity))
    this.transition.elapsed >= this.transition.duration && (this.stop(this.transition.easeOut), (this.transition.active = !1))
  }
  updateDefault(e, t, i, n) {
    const s = e / D
    t && (this.angularVelocity.addScaledVector(this.angularAccel, s), this.orbit(this.angularVelocity), this.angularVelocity.multiplyScalar(Math.pow(0.92, s)))
    i && (this.linearVelocity.addScaledVector(this.linearAccel, s), this.pan(this.linearVelocity), this.linearVelocity.multiplyScalar(Math.pow(0.92, s)))
    n && ((this.zoomVelocity += this.zoomAccel * s), this.zoom(this.zoomVelocity), (this.zoomVelocity *= Math.pow(0.84, s)))
  }
  startRotateTransition(e: number, t: THREE.Vector2, i: boolean) {
    t.x *= -1
    return this.startTransition(e, t.clone().multiplyScalar(D), new THREE.Vector2(), 0, i).nativePromise()
  }
  startTranslateTransition(e: number, t: THREE.Vector2, i = !0) {
    return this.startTransition(e, new THREE.Vector2(), t.clone().multiplyScalar(D), 0, i).nativePromise()
  }
  startZoomTransition(e: number, t: number, i: boolean) {
    return this.startTransition(e, new THREE.Vector2(0, 0), new THREE.Vector2(0, 0), t, i).nativePromise()
  }
  update(e) {
    const t = this.angularAccel.length() > N || this.angularVelocity.length() > N
    const i = this.linearAccel.length() > N || this.linearVelocity.length() > N
    const n = Math.abs(this.zoomAccel) > N || Math.abs(this.zoomVelocity) > N
    this.transition.active ? this.updateTransition(e, t, i, n) : this.updateDefault(e, t, i, n)
  }
  stopMomentum() {
    this.transition.active || (this.angularVelocity.set(0, 0), this.linearVelocity.set(0, 0), (this.zoomVelocity = 0))
  }
  stopAcceleration() {
    this.transition.active || (this.setOrbitalAcceleration({ x: 0, y: 0 }), this.setPanAcceleration({ x: 0, y: 0 }), this.setZoomAcceleration(0))
  }
  stop(e = !1) {
    this.stopTransition()
    this.stopAcceleration()
    e || this.stopMomentum()
  }
  pan(e) {
    if (!this.poseController) return
    const t = this.cameraPoseProxy.pose
    this.positionDelta.x = e.x
    this.positionDelta.z = e.y
    this.nextPosition.copy(t.position).add(this.positionDelta)
    if (!(this.checkBounds && !this.cameraCheck(this.nextPosition, t.rotation, t.focalDistance))) {
      this.currentPosition.copy(this.nextPosition)
      this.poseController.updateCameraPosition(this.currentPosition)
    }
  }
  orbit(e, t = !1) {
    if (!this.poseController) return
    const i = this.cameraPoseProxy.pose
    this.cameraOrientation.setFromQuaternion(i.rotation, "YXZ")
    const n = -this.cameraOrientation.x
    const s = funcUtil.getMax(e.y, this.currentPhiLowerLimit - n, this.currentPhiUpperLimit - n)
    this.aimTarget.copy(Vectors.FORWARD).applyQuaternion(i.rotation)
    this.aimTarget.setLength(i.focalDistance)
    this.aimTarget.addVectors(i.position, this.aimTarget)
    this.tempAxis.copy(Vectors.RIGHT)
    this.tempOrientation.setFromAxisAngle(this.tempAxis.applyQuaternion(i.rotation), -s)
    this.nextPosition.copy(i.position).sub(this.aimTarget).applyQuaternion(this.tempOrientation)
    this.nextOrientation.copy(i.rotation).premultiply(this.tempOrientation)
    this.tempOrientation.setFromAxisAngle(Vectors.UP, e.x)
    this.nextPosition.applyQuaternion(this.tempOrientation)
    this.nextOrientation.premultiply(this.tempOrientation)
    this.nextPosition = this.nextPosition.add(this.aimTarget)
    this.nextOrientation.normalize()
    if (!this.checkBounds || this.cameraCheck(i.position, this.nextOrientation, i.focalDistance)) {
      this.currentPosition.copy(this.nextPosition)
      this.currentOrientation.copy(this.nextOrientation)
      if (t) {
        return this.poseController.moveTo({
          transitionType: transitionTypeEnum.Interpolate,
          pose: {
            position: this.currentPosition.clone(),
            rotation: this.currentOrientation.clone()
          },
          transitionTime: 500
        })
      }
      i.rotation.equals(this.currentOrientation) ||
        (this.poseController.updateCameraPosition(this.currentPosition), this.poseController.updateCameraRotation(this.currentOrientation))
    }
  }
  zoom(e) {
    if (!this.poseController) return
    const t = this.cameraPoseProxy.pose
    const i = this.maxZoomDistance - 2
    const n = t.focalDistance
    const s = funcUtil.getMax(e * i + n, 2, this.maxZoomDistance) - n
    const o = t.focalDistance + s
    this.nextPosition.copy(t.position).add(Vectors.FORWARD.clone().applyQuaternion(t.rotation).setLength(-s)),
      (this.checkBounds && !this.cameraCheck(this.nextPosition, t.rotation, o)) ||
        (this.currentPosition.copy(this.nextPosition), this.poseController.updateCameraPosition(this.currentPosition), this.poseController.updateCameraFocus(o))
  }
}
